use codegraph_storage::{RedisStorage, RedisConfig};
use std::time::Duration;
use tokio::time::sleep;

/// Test Redis storage basic operations
#[tokio::test]
async fn test_redis_storage_basic_operations() {
    // Skip test if Redis is not available
    if !is_redis_available().await {
        println!("Skipping Redis tests - Redis server not available");
        return;
    }

    let config = RedisConfig {
        url: "redis://127.0.0.1:6379".to_string(),
        key_prefix: "test:".to_string(),
        ..Default::default()
    };

    let mut storage = match RedisStorage::new(config).await {
        Ok(s) => s,
        Err(_) => {
            println!("Skipping Redis tests - Cannot connect to Redis");
            return;
        }
    };

    // Clear any existing test data
    storage.clear().await.expect("Failed to clear storage");

    // Test store and retrieve
    let key = "test_key";
    let data = b"test_data";

    storage.store(key, &data[..]).await.expect("Failed to store data");
    
    let retrieved = storage.retrieve(key).await.expect("Failed to retrieve data");
    assert_eq!(retrieved, Some(data.to_vec()));

    // Test key exists
    assert!(storage.key_exists(key).await.expect("Failed to check key existence"));
    assert!(!storage.key_exists("non_existent_key").await.expect("Failed to check key existence"));

    // Test delete
    assert!(storage.delete(key).await.expect("Failed to delete key"));
    assert!(!storage.key_exists(key).await.expect("Failed to check key existence after delete"));

    // Test retrieve non-existent key
    let retrieved = storage.retrieve(key).await.expect("Failed to retrieve non-existent key");
    assert_eq!(retrieved, None);

    println!("✅ Redis storage basic operations test passed");
}

#[tokio::test]
async fn test_redis_storage_list_keys() {
    // Skip test if Redis is not available
    if !is_redis_available().await {
        println!("Skipping Redis tests - Redis server not available");
        return;
    }

    let config = RedisConfig {
        url: "redis://127.0.0.1:6379".to_string(),
        key_prefix: "test_list:".to_string(),
        ..Default::default()
    };

    let mut storage = match RedisStorage::new(config).await {
        Ok(s) => s,
        Err(_) => {
            println!("Skipping Redis tests - Cannot connect to Redis");
            return;
        }
    };

    // Clear any existing test data
    storage.clear().await.expect("Failed to clear storage");

    // Store multiple keys
    let keys_data = vec![
        ("key1", b"data1"),
        ("key2", b"data2"),
        ("prefix_key3", b"data3"),
        ("prefix_key4", b"data4"),
    ];

    for (key, data) in &keys_data {
        storage.store(key, &data[..]).await.expect("Failed to store data");
    }

    // Test list all keys
    let all_keys = storage.list_keys(None).await.expect("Failed to list all keys");
    assert_eq!(all_keys.len(), 4);
    assert!(all_keys.contains(&"key1".to_string()));
    assert!(all_keys.contains(&"key2".to_string()));
    assert!(all_keys.contains(&"prefix_key3".to_string()));
    assert!(all_keys.contains(&"prefix_key4".to_string()));

    // Test list keys with prefix
    let prefix_keys = storage.list_keys(Some("prefix_")).await.expect("Failed to list prefix keys");
    assert_eq!(prefix_keys.len(), 2);
    assert!(prefix_keys.contains(&"prefix_key3".to_string()));
    assert!(prefix_keys.contains(&"prefix_key4".to_string()));

    // Clean up
    storage.clear().await.expect("Failed to clear storage");

    println!("✅ Redis storage list keys test passed");
}

#[tokio::test]
async fn test_redis_storage_with_ttl() {
    // Skip test if Redis is not available
    if !is_redis_available().await {
        println!("Skipping Redis tests - Redis server not available");
        return;
    }

    let config = RedisConfig {
        url: "redis://127.0.0.1:6379".to_string(),
        key_prefix: "test_ttl:".to_string(),
        ttl: Some(1), // 1 second TTL
        ..Default::default()
    };

    let mut storage = match RedisStorage::new(config).await {
        Ok(s) => s,
        Err(_) => {
            println!("Skipping Redis tests - Cannot connect to Redis");
            return;
        }
    };

    // Clear any existing test data
    storage.clear().await.expect("Failed to clear storage");

    // Store data with TTL
    let key = "ttl_key";
    let data = b"ttl_data";
    
    storage.store(key, &data[..]).await.expect("Failed to store data with TTL");
    
    // Verify data exists immediately
    let retrieved = storage.retrieve(key).await.expect("Failed to retrieve data");
    assert_eq!(retrieved, Some(data.to_vec()));

    // Wait for TTL to expire
    sleep(Duration::from_secs(2)).await;

    // Verify data has expired
    let retrieved = storage.retrieve(key).await.expect("Failed to retrieve expired data");
    assert_eq!(retrieved, None);

    println!("✅ Redis storage TTL test passed");
}

#[tokio::test]
async fn test_distributed_lock() {
    // Skip test if Redis is not available
    if !is_redis_available().await {
        println!("Skipping Redis tests - Redis server not available");
        return;
    }

    let config = RedisConfig {
        url: "redis://127.0.0.1:6379".to_string(),
        key_prefix: "test_lock:".to_string(),
        ..Default::default()
    };

    let storage = match RedisStorage::new(config).await {
        Ok(s) => s,
        Err(_) => {
            println!("Skipping Redis tests - Cannot connect to Redis");
            return;
        }
    };

    let resource = "test_resource";
    let ttl = 5; // 5 seconds

    // Acquire lock
    let lock1 = storage.acquire_lock(resource, ttl).await
        .expect("Failed to acquire lock")
        .expect("Lock should be available");

    // Try to acquire the same lock (should fail)
    let lock2 = storage.acquire_lock(resource, ttl).await
        .expect("Failed to attempt lock acquisition");
    assert!(lock2.is_none(), "Second lock acquisition should fail");

    // Release the lock
    assert!(lock1.release().await.expect("Failed to release lock"));

    // Now we should be able to acquire the lock again
    let lock3 = storage.acquire_lock(resource, ttl).await
        .expect("Failed to acquire lock after release")
        .expect("Lock should be available after release");

    // Clean up
    lock3.release().await.expect("Failed to release final lock");

    println!("✅ Distributed lock test passed");
}

#[tokio::test]
async fn test_distributed_lock_extend() {
    // Skip test if Redis is not available
    if !is_redis_available().await {
        println!("Skipping Redis tests - Redis server not available");
        return;
    }

    let config = RedisConfig {
        url: "redis://127.0.0.1:6379".to_string(),
        key_prefix: "test_lock_extend:".to_string(),
        ..Default::default()
    };

    let storage = match RedisStorage::new(config).await {
        Ok(s) => s,
        Err(_) => {
            println!("Skipping Redis tests - Cannot connect to Redis");
            return;
        }
    };

    let resource = "test_resource_extend";
    let initial_ttl = 2; // 2 seconds

    // Acquire lock
    let mut lock = storage.acquire_lock(resource, initial_ttl).await
        .expect("Failed to acquire lock")
        .expect("Lock should be available");

    // Extend the lock
    assert!(lock.extend(3).await.expect("Failed to extend lock"));

    // Wait for original TTL to expire
    sleep(Duration::from_secs(3)).await;

    // Lock should still be valid due to extension
    // Try to acquire the same lock (should still fail)
    let lock2 = storage.acquire_lock(resource, 1).await
        .expect("Failed to attempt lock acquisition");
    assert!(lock2.is_none(), "Lock should still be held after extension");

    // Clean up
    lock.release().await.expect("Failed to release extended lock");

    println!("✅ Distributed lock extend test passed");
}

#[tokio::test]
async fn test_redis_storage_performance() {
    // Skip test if Redis is not available
    if !is_redis_available().await {
        println!("Skipping Redis tests - Redis server not available");
        return;
    }

    let config = RedisConfig {
        url: "redis://127.0.0.1:6379".to_string(),
        key_prefix: "test_perf:".to_string(),
        ..Default::default()
    };

    let mut storage = match RedisStorage::new(config).await {
        Ok(s) => s,
        Err(_) => {
            println!("Skipping Redis tests - Cannot connect to Redis");
            return;
        }
    };

    // Clear any existing test data
    storage.clear().await.expect("Failed to clear storage");

    let num_operations = 100;
    let data = b"performance_test_data";

    // Test write performance
    let start_time = std::time::Instant::now();
    for i in 0..num_operations {
        let key = format!("perf_key_{}", i);
        storage.store(&key, &data[..]).await.expect("Failed to store data");
    }
    let write_duration = start_time.elapsed();

    // Test read performance
    let start_time = std::time::Instant::now();
    for i in 0..num_operations {
        let key = format!("perf_key_{}", i);
        let retrieved = storage.retrieve(&key).await.expect("Failed to retrieve data");
        assert_eq!(retrieved, Some(data.to_vec()));
    }
    let read_duration = start_time.elapsed();

    // Test list performance
    let start_time = std::time::Instant::now();
    let keys = storage.list_keys(Some("perf_")).await.expect("Failed to list keys");
    let list_duration = start_time.elapsed();
    assert_eq!(keys.len(), num_operations);

    println!("✅ Redis storage performance test completed:");
    println!("   Write {} operations: {:?} ({:.2} ops/sec)", 
             num_operations, write_duration, 
             num_operations as f64 / write_duration.as_secs_f64());
    println!("   Read {} operations: {:?} ({:.2} ops/sec)", 
             num_operations, read_duration, 
             num_operations as f64 / read_duration.as_secs_f64());
    println!("   List {} keys: {:?}", num_operations, list_duration);

    // Clean up
    storage.clear().await.expect("Failed to clear storage");
}

/// Helper function to check if Redis is available
async fn is_redis_available() -> bool {
    match redis::Client::open("redis://127.0.0.1:6379") {
        Ok(client) => {
            match client.get_async_connection().await {
                Ok(mut conn) => {
                    // Try to ping Redis
                    match redis::cmd("PING").query_async::<_, String>(&mut conn).await {
                        Ok(_) => true,
                        Err(_) => false,
                    }
                }
                Err(_) => false,
            }
        }
        Err(_) => false,
    }
}
